﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using DACU.Player.Enums;
using DACU.Tools;
using DACU.VkontakteApi;
using DACU.VkontakteApi.Collections;
using Un4seen.Bass;
using System.IO;
using Timer = System.Timers.Timer;

namespace DACU.Player
{
	class MediaPlayer
	{
		//private BASSTimer timer, downlTimer;
		System.Timers.Timer timer, downlTimer;
		public event ChangedBytesPosEventHandler ChangedBytesPos;
		public event ChangedSecondsPosEventHandler ChangedSecondsPos;
		public event DownloadStateEventHandler DownloadStateChanged;
		public event EventHandler TrackEnded;
		public event EventHandler ActivityChanged;
		Dictionary<int, string> _plugins = new Dictionary<int, string>();
		private Track _prevTrack;
		SYNCPROC _mySync;
		//private DOWNLOADPROC _myDownload;
		int synchronizer;
		//private bool isUrl;

		int stream;
		public int Handle
		{
			get { return stream; }
		}

		private Track _currentTrack;
		public Track CurrentTrack
		{
			get { return _currentTrack; }
		}

		private BASSActive activity = BASSActive.BASS_ACTIVE_STOPPED;
		public BASSActive Activity
		{
			get
			{
				return activity == BASSActive.BASS_ACTIVE_STALLED ? Bass.BASS_ChannelIsActive(stream) : activity;
			}
		}

		private float _volume = float.NaN;
		public float Volume
		{
			get
			{
				if (float.IsNaN(_volume))
					return _volume = GetVolume();
				return _volume;
			}
			set
			{
				if (_volume == value && value == Volume) return;
				_volume = value;
				SetVolume(_volume);
			}
		}

		public MediaPlayer()
		{
			if(File.Exists("bass.key"))
			{
				var s = File.ReadAllLines("bass.key");
				if(s!=null && s.Length>1)
					BassNet.Registration(s[0], s[1]);
			}
			if (!Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero))
				ThrowException();
			LoadLibs();
			_mySync = new SYNCPROC(EndSync);
			timer = new Timer(500); //new BASSTimer(500);
			downlTimer = new Timer(1000); //new BASSTimer(1000);
			timer.Elapsed += TimerCall;
			downlTimer.Elapsed += DownltimerCall;
			//_myDownload = new DOWNLOADPROC(DownloadProcess);
		}

		void LoadLibs()
		{
			if (Directory.Exists(ConfigTools.LibsFolder) && Directory.GetFiles(ConfigTools.LibsFolder).Length > 0)
			{
				Dictionary<int,string> plugins = Bass.BASS_PluginLoadDirectory(ConfigTools.LibsFolder);
				if (plugins != null)
					_plugins.Concat(plugins);
			}
		}

		BassException ThrowException(bool onlyReturn = false)
		{
			if(timer!=null && timer.Enabled) timer.Stop();
			var bassException = new BassException(Bass.BASS_ErrorGetCode());
			switch (bassException.BassError)
			{
				case BASSError.BASS_ERROR_ALREADY:
				case BASSError.BASS_ERROR_NOTAVAIL:
					return null;
			}
			if (onlyReturn) return bassException;
			throw bassException;
		}

		void ChangeActivity()
		{
			if(ActivityChanged!=null)
				ActivityChanged(this,new EventArgs());
		}

		//private void DownloadProcess(IntPtr buffer, int length, IntPtr user)
		//{
		//    string txt = Marshal.PtrToStringAnsi(buffer);
		//}

		private void EndSync(int handle, int channel, int data, IntPtr user)
		{
			if (TrackEnded != null)
				TrackEnded(this, new EventArgs());
			ChangeActivity();
		}

		void BassStreamFree()
		{
			if (stream != 0)
			{
				Bass.BASS_StreamFree(stream);
				activity = BASSActive.BASS_ACTIVE_STOPPED;
				ChangeActivity();
			}
		}

		void ChangeCurrentTrackState(bool isPlayed)
		{
			if (_currentTrack != null)
				_currentTrack.IsCurrentlyPlays = isPlayed;
		}

		public void LoadTrack(Track track)
		{
			if (track == null || track.IsEmpty)
			{
				_prevTrack = _currentTrack;
				_currentTrack = null;
				return;
			}
			if (track != _currentTrack)
			{
				_prevTrack = _currentTrack;
				_currentTrack = track;
				if (_prevTrack != null && _prevTrack.IsCurrentlyPlays) _prevTrack.IsCurrentlyPlays = false;
			}
			switch (track.Type)
			{
				case TrackType.File:
					LoadFile(track.Path);
					break;
				case TrackType.Url:
				case TrackType.VKontakte:
					LoadUrl(track.Path);
					break;
				default:
					return;
			}
		}

		public void LoadSong(string location)
		{
			if (String.IsNullOrWhiteSpace(location)) return;
			Uri url;
			if (Uri.TryCreate(location, UriKind.RelativeOrAbsolute, out url))
				if (url.IsFile)
					LoadFile(location);
				else
					LoadUrl(location);
		}

		void LoadFile(string location)
		{
			BassStreamFree();
			timer.Stop();
			stream = Bass.BASS_StreamCreateFile(location, 0, 0, BASSFlag.BASS_SAMPLE_FLOAT);
			try
			{
				if (stream == 0) ThrowException();
			}
			catch (BassException ex)
			{
				switch (ex.BassError)
				{
					case BASSError.BASS_ERROR_INIT:
						Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero);
					break;
					default:
						throw;
				}
			}
			SetVolume(Volume);
			timer.Start();
			synchronizer = Bass.BASS_ChannelSetSync(stream, BASSSync.BASS_SYNC_END, 0, _mySync, IntPtr.Zero);
			if (synchronizer == 0) ThrowException();
		}

		void LoadUrl(string url)
		{
			BassStreamFree();
			timer.Stop();
			stream = Bass.BASS_StreamCreateURL(url, 0, BASSFlag.BASS_DEFAULT | BASSFlag.BASS_STREAM_STATUS, /*_myDownload*/null, IntPtr.Zero);
			try
			{
				if (stream == 0) ThrowException();
			}
			catch (BassException ex)
			{
				switch (ex.BassError)
				{
					case BASSError.BASS_ERROR_INIT:
						Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero);
						break;
					case BASSError.BASS_ERROR_FILEOPEN:
						if(_currentTrack!=null && _currentTrack.Type == TrackType.VKontakte)
						{
							var coll = VKApi.GetAudioCollection(aids: new VKIdsCollectionHelper<int> {_currentTrack.VKid});
							if (coll != null && !coll.IsEmpty)
							{
								_currentTrack.Path = coll[0].Url;
								stream = Bass.BASS_StreamCreateURL(_currentTrack.Path, 0, BASSFlag.BASS_DEFAULT | BASSFlag.BASS_STREAM_STATUS, /*_myDownload*/null, IntPtr.Zero);
								if (stream == 0) ThrowException();
							}
						}
						break;
					default:
						throw;
				}
			}
			SetVolume(Volume);
			timer.Start();
			downlTimer.Start();
			synchronizer = Bass.BASS_ChannelSetSync(stream, BASSSync.BASS_SYNC_END, 0, _mySync, IntPtr.Zero);
			if (synchronizer == 0) ThrowException();
			//isUrl = true;
		}

		void Play()
		{
			if (Bass.BASS_ChannelPlay(stream, Activity == BASSActive.BASS_ACTIVE_STOPPED))
			{
				activity = BASSActive.BASS_ACTIVE_PLAYING;
				timer.Start();
				ChangeCurrentTrackState(true);
				ChangeActivity();
			}
			else ThrowException();
		}

		public void PlaySong()
		{
			if (stream != 0)
				Play();
			else
				if(_currentTrack!=null)
				{
					LoadTrack(_currentTrack);
					Play();
				}
		}

		public void StopSong()
		{
			if (stream != 0)
			{
				if (Bass.BASS_ChannelStop(stream))
				{
					activity = BASSActive.BASS_ACTIVE_STOPPED;
					ChangeCurrentTrackState(false);
					timer.Stop();
					ChangeActivity();
				}
				else ThrowException();
			}
			else
				ChangeCurrentTrackState(false);
			//isUrl = false;
		}

		void Pause()
		{
			switch (Activity)
			{
				case BASSActive.BASS_ACTIVE_PLAYING:
					if (Bass.BASS_ChannelPause(stream))
					{
						activity = BASSActive.BASS_ACTIVE_PAUSED;
						timer.Stop();
						ChangeActivity();
					}
					else ThrowException();
					break;
				case BASSActive.BASS_ACTIVE_STOPPED:
				case BASSActive.BASS_ACTIVE_PAUSED:
					PlaySong();
					break;
			}
		}

		public void PauseSong()
		{
			if (stream != 0)
				Pause();
			else
				if(_currentTrack!=null)
				if(_currentTrack.IsCurrentlyPlays)
				{
					LoadTrack(_currentTrack);
					Pause();
				}
		}

		public void PlayNextTrack(Track track, bool stopOnly = false)
		{
			if (Handle != 0)
				Free();
			if (stopOnly) return;
			LoadTrack(track);
			Volume = _volume;
			PlaySong();
		}

		public void PlayNextSong(string fileUrl, bool stopOnly = false)
		{
			if (Handle != 0)
				Free();
			if (stopOnly) return;
			LoadSong(fileUrl);
			Volume = _volume;
			PlaySong();
		}

		float GetVolume()
		{
			float level = 0;
			if (stream != 0)
			if(!Bass.BASS_ChannelGetAttribute(stream, BASSAttribute.BASS_ATTRIB_VOL, ref level))
				ThrowException();
			return level*100;//Bass.BASS_GetVolume();
		}

		void SetVolume(float persent)
		{
			if (persent > 100f) persent = 100f;
			if (persent < 0f) persent = 0f;
			if(stream!=0)
			if (!Bass.BASS_ChannelSetAttribute(stream, BASSAttribute.BASS_ATTRIB_VOL, persent / 100))
				ThrowException();
			ConfigTools.ConfigFile.Volume = persent;
		}

		public TimeSpan GetTime(int handle,long pos)
		{
			TimeSpan time;
			double d = Bass.BASS_ChannelBytes2Seconds(handle, pos);
			if(d<0) ThrowException();
			string s = TimeSpan.FromSeconds(d).ToString(@"hh\:mm\:ss");
			TimeSpan.TryParse(s,out time);
			return time;
		}

		public long GetBytesLength()
		{
			if (stream != 0)
			{
				long length = Bass.BASS_ChannelGetLength(stream);
				if (length == -1) ThrowException();
				return length;
			}
			return -1;
		}

		public double GetSecondsLength()
		{
			long bytes = GetBytesLength();
			if (bytes > -1 && stream != 0)
			{
				double seconds = Bass.BASS_ChannelBytes2Seconds(stream, bytes);
				if (seconds < 0) ThrowException();
				return seconds;
			}
			return -1;
		}

		public TimeSpan GetTimeLength()
		{
			return GetTime(stream,GetBytesLength());
		}

		public long GetBytesPosition()
		{
			if (stream != 0)
			{
				long position = Bass.BASS_ChannelGetPosition(stream);
				//if(position==-1) ThrowException();
				return position;
			}
			return -1;
		}

		public double GetSecondsPosition()
		{
			long pos = GetBytesPosition();
			if (pos > -1 && stream != 0)
			{
				double seconds = Bass.BASS_ChannelBytes2Seconds(stream, pos);
				return seconds;
			}
			return -1;
		}

		public TimeSpan GetTimePosition()
		{
			return GetTime(stream,GetBytesPosition());
		}

		public void SetPosition(double seconds)
		{
			if (stream != 0)
			if(!Bass.BASS_ChannelSetPosition(stream, seconds))
				ThrowException();
		}

		void DownltimerCall(object sender, EventArgs e)
		{
			if (stream == 0) return;
			if (DownloadStateChanged != null)
			{
				long length = Bass.BASS_StreamGetFilePosition(stream, BASSStreamFilePosition.BASS_FILEPOS_END);
				if (length > -1)
				{
					long down = Bass.BASS_StreamGetFilePosition(stream, BASSStreamFilePosition.BASS_FILEPOS_DOWNLOAD);
					if (down == -1) ThrowException();
					float persent = down * 100F / length;
					if (persent >= 100F) downlTimer.Stop();
					DownloadStateChanged(persent);
				}
				else ThrowException();
			}
		}

		void TimerCall(object sender,EventArgs e)
		{
			if (stream == 0) return;
			if (ChangedBytesPos != null || ChangedSecondsPos != null)
			{
				long pos = GetBytesPosition();
				if (pos > -1)
				{
					if (ChangedBytesPos != null)
						ChangedBytesPos(pos);
					if(ChangedSecondsPos!=null)
					{
						double seconds = Bass.BASS_ChannelBytes2Seconds(stream, pos);
						if (seconds < 0) ThrowException();
						ChangedSecondsPos(seconds);
					}
					
				}
			}
		}

		public void Free()
		{
			downlTimer.Stop();
			timer.Stop();
			Bass.BASS_ChannelRemoveSync(stream, synchronizer);
			ChangeCurrentTrackState(false);
			BassStreamFree();
			stream = 0;
			//isUrl = false;
			//Bass.BASS_Free();
		}

		~MediaPlayer()
		{
			timer.Enabled = false;
			timer.Elapsed -= TimerCall;
			downlTimer.Enabled = false;
			downlTimer.Elapsed -= DownltimerCall;
			foreach (int key in _plugins.Keys)
				Bass.BASS_PluginFree(key);
			Bass.BASS_ChannelRemoveSync(stream, synchronizer);
			Bass.BASS_StreamFree(stream);
			Bass.BASS_Free();
		}
	}
}
